/*
 * Copyright (C) 2004 NNL Technology AB
 * Visit www.infonode.net for information about InfoNode(R) 
 * products and how to contact NNL Technology AB.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
 * MA 02111-1307, USA.
 */

// $Id: TitledTab.java,v 1.89 2009/02/05 15:57:56 jesper Exp $
package net.infonode.tabbedpanel.titledtab;

import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;

import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.PanelUI;

import net.infonode.gui.DimensionProvider;
import net.infonode.gui.InsetsUtil;
import net.infonode.gui.RotatableLabel;
import net.infonode.gui.TranslatingShape;
import net.infonode.gui.border.FocusBorder;
import net.infonode.gui.hover.HoverEvent;
import net.infonode.gui.hover.HoverListener;
import net.infonode.gui.hover.hoverable.HoverManager;
import net.infonode.gui.hover.hoverable.Hoverable;
import net.infonode.gui.icon.IconProvider;
import net.infonode.gui.layout.StackableLayout;
import net.infonode.gui.panel.SimplePanel;
import net.infonode.gui.shaped.panel.ShapedPanel;
import net.infonode.properties.base.Property;
import net.infonode.properties.gui.InternalPropertiesUtil;
import net.infonode.properties.gui.util.ComponentProperties;
import net.infonode.properties.gui.util.ShapedPanelProperties;
import net.infonode.properties.propertymap.PropertyMapTreeListener;
import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
import net.infonode.properties.util.PropertyChangeListener;
import net.infonode.tabbedpanel.*;
import net.infonode.util.Alignment;
import net.infonode.util.Direction;
import net.infonode.util.ValueChange;

/**
 * <p>
 * A TitledTab is a tab that has support for text, icon and a custom Swing component (called title component). Titled tab supports several properties that makes
 * it possible to change the look (borders, colors, insets), layout (up, down, left, right).
 * </p>
 *
 * <p>
 * Titled tab has a line based layout, i.e. the text, icon and title component are laid out in a line. The layout of the tab can be rotated, i.e. the text and
 * the icon will be rotated 90, 180 or 270 degrees. The title component will not be rotated but moved so that the line layout will persist.
 * </p>
 *
 * <p>
 * A titled tab has 3 rendering states:
 * <ul>
 * <li>Normal - The tab is selectable but not yet selected
 * <li>Highlighted - The tab is either highlighted or selected
 * <li>Disabled - The tab is disabled and cannot be selected or highlighted
 * </ul>
 * Most of the properties for the tab can be configured for each of the tab rendering states.
 * </p>
 *
 * <p>
 * <strong>Note:</strong> If only the normal state properties have been configured, the highlighted and disabled state will automatically use the same
 * properties as for the normal state, see {@link TitledTabProperties} and {@link TitledTabStateProperties}.
 * </p>
 *
 * <p>
 * TitledTab implements the {@link net.infonode.gui.icon.IconProvider} interface and overloads toString() so that both text and icon for the normal state is
 * shown in the tab drop down list in a tabbed panel.
 * </p>
 *
 * <p>
 * TitledTab supports mouse hovering. A {@link HoverListener} can be set in the {@link TitledTabProperties}. The hover listener receives a {@link HoverEvent}
 * when the mouse enters or exits the tab. The hover event's source will be the affected titled tab.
 * </p>
 *
 * @author $Author: jesper $
 * @version $Revision: 1.89 $
 * @see TitledTabProperties
 * @see TitledTabStateProperties
 */
public class TitledTab extends Tab implements IconProvider {
	private static PanelUI UI = new PanelUI() {
	};

	private class StatePanel extends SimplePanel {
		private final ShapedPanel panel = new ShapedPanel();
		private final SimplePanel titleComponentPanel = new SimplePanel();
		private final RotatableLabel label = new RotatableLabel(null, null) {
			public Dimension getPreferredSize() {
				Dimension d = super.getPreferredSize();
				String text = this.getText();
				Icon tmpIcon = this.getIcon();

				if (text == null || tmpIcon == null) {
					this.setText(" ");
					this.setIcon(icon);
					if (getDirection().isHorizontal())
						d = new Dimension(d.width, super.getPreferredSize().height);
					else
						d = new Dimension(super.getPreferredSize().width, d.height);

					this.setText(text);
					this.setIcon(tmpIcon);
				}

				return d;
			}
		};
		private JComponent titleComponent;
		private Direction currentLayoutDirection;
		private int currentLayoutGap = -1;
		private Alignment currentLayoutAlignment;
		private String toolTipText;
		private Icon icon;

		public StatePanel(Border focusBorder) {
			super(new BorderLayout());

			label.setBorder(focusBorder);
			label.setMinimumSize(new Dimension(0, 0));

			panel.add(label, BorderLayout.CENTER);
			add(panel, BorderLayout.CENTER);
		}

		public String getToolTipText() {
			return toolTipText;
		}

		public JComponent getTitleComponent() {
			return titleComponent;
		}

		public Shape getShape() {
			return panel.getShape();
		}

		public JLabel getLabel() {
			return label;
		}

		public void setTitleComponent(JComponent titleComponent, TitledTabStateProperties stateProps) {
			JComponent oldTitleComponent = this.titleComponent;
			this.titleComponent = null;
			if (oldTitleComponent != null && oldTitleComponent.getParent() == titleComponentPanel)
				titleComponentPanel.remove(oldTitleComponent);
			this.titleComponent = titleComponent;
			updateLayout(stateProps, true);
		}

		public void activateTitleComponent() {
			if (titleComponent != null) {
				if (titleComponent.getParent() != titleComponentPanel) {
					if (titleComponent.getParent() != null)
						titleComponent.getParent().remove(titleComponent);
					titleComponentPanel.add(titleComponent, BorderLayout.CENTER);
				}
			} else {
				titleComponentPanel.removeAll();
			}
		}

		public void activate() {
			remove(panel);
			eventPanel.add(panel, BorderLayout.CENTER);
			add(eventPanel, BorderLayout.CENTER);
		}

		public void deactivate() {
			remove(eventPanel);
			eventPanel.remove(panel);
			add(panel, BorderLayout.CENTER);
		}

		public Dimension getPreferredSize() {
			activateTitleComponent();

			return getAdjustedSize(super.getPreferredSize());
		}

		public Dimension getMinimumSize() {
			activateTitleComponent();

			return getAdjustedSize(super.getMinimumSize());
		}

		public Dimension getMaximumSize() {
			activateTitleComponent();
			return super.getMaximumSize();
		}

		private Dimension getAdjustedSize(Dimension d) {
			DimensionProvider prov = properties.getMinimumSizeProvider();
			if (prov == null)
				return d;

			Dimension min = properties.getMinimumSizeProvider().getDimension(this);

			if (min == null)
				return d;

			return new Dimension(Math.max(min.width, d.width), Math.max(min.height, d.height));
		}

		public JComponent getFocusableComponent() {
			return label;
		}

		private void updateLayout(TitledTabStateProperties stateProperties, boolean titleComponentChanged) {
			if (titleComponent != null && stateProperties.getTitleComponentVisible()) {
				Direction d = stateProperties.getDirection();
				int gap;
				if (stateProperties.getIconVisible() || stateProperties.getTextVisible())
					gap = stateProperties.getTextTitleComponentGap();
				else
					gap = 0;
				Alignment alignment = stateProperties.getTitleComponentTextRelativeAlignment();
				if (titleComponentPanel.getComponentCount() == 0
						|| (titleComponentPanel.getComponentCount() > 0 && titleComponentPanel.getComponent(0) != titleComponent) || titleComponentChanged
						|| gap != currentLayoutGap || alignment != currentLayoutAlignment || d != currentLayoutDirection) {
					titleComponentChanged = false;
					currentLayoutDirection = d;
					currentLayoutGap = gap;
					currentLayoutAlignment = alignment;

					panel.remove(titleComponentPanel);
					if (d == Direction.UP) {
						panel.add(titleComponentPanel, alignment == Alignment.LEFT ? BorderLayout.SOUTH : BorderLayout.NORTH);
						titleComponentPanel.setBorder(new EmptyBorder(alignment == Alignment.LEFT ? gap : 0, 0, alignment == Alignment.LEFT ? 0 : gap, 0));
					} else if (d == Direction.LEFT) {
						panel.add(titleComponentPanel, alignment == Alignment.LEFT ? BorderLayout.EAST : BorderLayout.WEST);
						titleComponentPanel.setBorder(new EmptyBorder(0, alignment == Alignment.LEFT ? gap : 0, 0, alignment == Alignment.LEFT ? 0 : gap));
					} else if (d == Direction.DOWN) {
						panel.add(titleComponentPanel, alignment == Alignment.LEFT ? BorderLayout.NORTH : BorderLayout.SOUTH);
						titleComponentPanel.setBorder(new EmptyBorder(alignment == Alignment.LEFT ? 0 : gap, 0, alignment == Alignment.LEFT ? gap : 0, 0));
					} else {
						panel.add(titleComponentPanel, alignment == Alignment.LEFT ? BorderLayout.WEST : BorderLayout.EAST);
						titleComponentPanel.setBorder(new EmptyBorder(0, alignment == Alignment.LEFT ? 0 : gap, 0, alignment == Alignment.LEFT ? gap : 0));
					}

					panel.revalidate();
				}
			} else {
				panel.remove(titleComponentPanel);
				titleComponentPanel.removeAll();

				panel.revalidate();
			}
		}

		public void updateShapedPanel(TitledTabStateProperties stateProperties) {
			Direction tabAreaOrientation = getTabAreaOrientation();
			ShapedPanelProperties shapedPanelProperties = stateProperties.getShapedPanelProperties();
			InternalPropertiesUtil.applyTo(shapedPanelProperties, panel, tabAreaOrientation.getNextCW());
			panel.setHorizontalFlip(tabAreaOrientation == Direction.DOWN || tabAreaOrientation == Direction.LEFT ? !shapedPanelProperties.getHorizontalFlip()
					: shapedPanelProperties.getHorizontalFlip());
		}

		public void setBorders(Border outerBorder, Border innerBorder) {
			setBorder(outerBorder);
			panel.setBorder(innerBorder);
		}

		public boolean updateState(Map changes, TitledTabStateProperties stateProperties) {
			boolean updateBorders = false;

			if (changes == null) {
				label.setText(stateProperties.getTextVisible() ? stateProperties.getText() : null);
				icon = stateProperties.getIcon();
				label.setIcon(stateProperties.getIconVisible() ? stateProperties.getIcon() : null);

				label.setIconTextGap(stateProperties.getIconTextGap());

				label.setDirection(stateProperties.getDirection());

				Alignment alignment = stateProperties.getIconTextRelativeAlignment();
				label.setHorizontalTextPosition(alignment == Alignment.LEFT ? JLabel.RIGHT : JLabel.LEFT);

				alignment = stateProperties.getHorizontalAlignment();
				label.setHorizontalAlignment(alignment == Alignment.LEFT ? JLabel.LEFT : alignment == Alignment.CENTER ? JLabel.CENTER : JLabel.RIGHT);

				alignment = stateProperties.getVerticalAlignment();
				label.setVerticalAlignment(alignment == Alignment.TOP ? JLabel.TOP : alignment == Alignment.CENTER ? JLabel.CENTER : JLabel.BOTTOM);

				toolTipText = stateProperties.getToolTipEnabled() ? stateProperties.getToolTipText() : null;
				if (toolTipText != null && toolTipText.length() == 0)
					toolTipText = null;
				if (currentStatePanel == this)
					eventPanel.setToolTipText(toolTipText);

				updateLayout(stateProperties, true);

				ComponentProperties componentProperties = stateProperties.getComponentProperties();
				label.setFont(componentProperties.getFont());

				Color c = componentProperties.getForegroundColor();
				label.setForeground(c);
				setForeground(c);

				updateShapedPanel(stateProperties);

				updateBorders = true;
			} else {
				Map m = (Map) changes.get(stateProperties.getMap());
				if (m != null) {
					Set keySet = m.keySet();

					if (keySet.contains(TitledTabStateProperties.TEXT) || keySet.contains(TitledTabStateProperties.TEXT_VISIBLE)) {
						label.setText(stateProperties.getTextVisible() ? stateProperties.getText() : null);
					}

					if (keySet.contains(TitledTabStateProperties.ICON) || keySet.contains(TitledTabStateProperties.ICON_VISIBLE)) {
						icon = stateProperties.getIcon();
						label.setIcon(stateProperties.getIconVisible() ? stateProperties.getIcon() : null);
					}

					if (keySet.contains(TitledTabStateProperties.ICON_TEXT_GAP)) {
						label.setIconTextGap(((Integer) ((ValueChange) m.get(TitledTabStateProperties.ICON_TEXT_GAP)).getNewValue()).intValue());
					}

					if (keySet.contains(TitledTabStateProperties.ICON_TEXT_RELATIVE_ALIGNMENT)) {
						Alignment alignment = (Alignment) ((ValueChange) m.get(TitledTabStateProperties.ICON_TEXT_RELATIVE_ALIGNMENT)).getNewValue();
						label.setHorizontalTextPosition(alignment == Alignment.LEFT ? JLabel.RIGHT : JLabel.LEFT);
					}

					if (keySet.contains(TitledTabStateProperties.HORIZONTAL_ALIGNMENT)) {
						Alignment alignment = (Alignment) ((ValueChange) m.get(TitledTabStateProperties.HORIZONTAL_ALIGNMENT)).getNewValue();
						label.setHorizontalAlignment(alignment == Alignment.LEFT ? JLabel.LEFT : alignment == Alignment.CENTER ? JLabel.CENTER : JLabel.RIGHT);
					}

					if (keySet.contains(TitledTabStateProperties.VERTICAL_ALIGNMENT)) {
						Alignment alignment = (Alignment) ((ValueChange) m.get(TitledTabStateProperties.VERTICAL_ALIGNMENT)).getNewValue();
						label.setVerticalAlignment(alignment == Alignment.TOP ? JLabel.TOP : alignment == Alignment.CENTER ? JLabel.CENTER : JLabel.BOTTOM);
					}

					if (keySet.contains(TitledTabStateProperties.TOOL_TIP_TEXT) || keySet.contains(TitledTabStateProperties.TOOL_TIP_ENABLED)) {
						toolTipText = stateProperties.getToolTipEnabled() ? stateProperties.getToolTipText() : null;
						if (toolTipText != null && toolTipText.length() == 0)
							toolTipText = null;

						if (currentStatePanel == this)
							eventPanel.setToolTipText(toolTipText);
					}

					if (keySet.contains(TitledTabStateProperties.DIRECTION)
							|| keySet.contains(TitledTabStateProperties.TITLE_COMPONENT_TEXT_RELATIVE_ALIGNMENT)
							|| keySet.contains(TitledTabStateProperties.TITLE_COMPONENT_VISIBLE)
							|| keySet.contains(TitledTabStateProperties.TEXT_TITLE_COMPONENT_GAP) || keySet.contains(TitledTabStateProperties.ICON_VISIBLE)
							|| keySet.contains(TitledTabStateProperties.TEXT_VISIBLE)) {
						label.setDirection(stateProperties.getDirection());

						updateLayout(stateProperties, keySet.contains(TitledTabStateProperties.TITLE_COMPONENT_VISIBLE));
					}

					if (keySet.contains(TitledTabStateProperties.DIRECTION)) {
						updateBorders = true;
					}
				}

				m = (Map) changes.get(stateProperties.getComponentProperties().getMap());
				if (m != null) {
					Set keySet = m.keySet();

					if (keySet.contains(ComponentProperties.FONT)) {
						label.setFont((Font) ((ValueChange) m.get(ComponentProperties.FONT)).getNewValue());
					}

					if (keySet.contains(ComponentProperties.FOREGROUND_COLOR)) {
						Color c = (Color) ((ValueChange) m.get(ComponentProperties.FOREGROUND_COLOR)).getNewValue();
						label.setForeground(c);
						setForeground(c);
					}

					if (keySet.contains(ComponentProperties.BACKGROUND_COLOR)) {
						Color c = (Color) ((ValueChange) m.get(ComponentProperties.BACKGROUND_COLOR)).getNewValue();
						panel.setBackground(c);
					}

					if (keySet.contains(ComponentProperties.INSETS) || keySet.contains(ComponentProperties.BORDER)) {
						updateBorders = true;
					}
				}

				m = (Map) changes.get(stateProperties.getShapedPanelProperties().getMap());
				if (m != null) {
					updateShapedPanel(stateProperties);
				}
			}

			return updateBorders;
		}
	}

	private final TitledTabProperties properties = TitledTabProperties.getDefaultProperties();

	private HoverListener hoverListener = properties.getHoverListener();

	private class HoverablePanel extends SimplePanel implements Hoverable {
		public HoverablePanel(LayoutManager l) {
			super(l);
		}

		public void hoverEnter() {
			if (hoverListener != null && getTabbedPanel() != null)
				hoverListener.mouseEntered(new HoverEvent(TitledTab.this));
		}

		public void hoverExit() {
			if (hoverListener != null)
				hoverListener.mouseExited(new HoverEvent(TitledTab.this));
		}

		public boolean acceptHover(ArrayList enterableHoverables) {
			return true;
		}
	}

	private final HoverablePanel eventPanel = new HoverablePanel(new BorderLayout()) {

		public boolean contains(int x, int y) {
			return getComponentCount() > 0 && getComponent(0).contains(x, y);
		}

		public boolean inside(int x, int y) {
			return getComponentCount() > 0 && getComponent(0).inside(x, y);
		}

	};

	public boolean contains(int x, int y) {
		Point p = SwingUtilities.convertPoint(this, new Point(x, y), eventPanel);
		return eventPanel.contains(p.x, p.y);
	}

	public boolean inside(int x, int y) {
		Point p = SwingUtilities.convertPoint(this, new Point(x, y), eventPanel);
		return eventPanel.inside(p.x, p.y);
	}

	private final StatePanel normalStatePanel;
	private final StatePanel highlightedStatePanel;
	private final StatePanel disabledStatePanel;

	private ArrayList mouseListeners;
	private ArrayList mouseMotionListeners;
	private final StackableLayout layout;
	private StatePanel currentStatePanel;
	private final FocusBorder focusBorder;

	private Direction lastTabAreaOrientation = Direction.UP;

	private final PropertyMapTreeListener propertiesListener = new PropertyMapTreeListener() {
		public void propertyValuesChanged(Map changes) {
			doUpdateTab(changes);
		}
	};

	private final PropertyChangeListener tabbedPanelPropertiesListener = new PropertyChangeListener() {
		public void propertyChanged(Property property, Object valueContainer, Object oldValue, Object newValue) {
			updateTabAreaOrientation((Direction) newValue);
		}
	};

	/*
	 * private FocusListener focusListener = new FocusListener() { public void focusGained(FocusEvent e) { if (properties.getFocusable()) repaint(); }
	 * 
	 * public void focusLost(FocusEvent e) { if (properties.getFocusable()) repaint(); } };
	 */

	/**
	 * Constructs a TitledTab with a text, icon, content component and title component.
	 *
	 * @param text
	 *            text or null for no text. The text will be applied to the normal state properties
	 * @param icon
	 *            icon or null for no icon. The icon will be applied to the normal state properties
	 * @param contentComponent
	 *            content component or null for no content component
	 * @param titleComponent
	 *            title component or null for no title component. The title component will be applied to all the states
	 * @see net.infonode.tabbedpanel.TabFactory
	 */
	public TitledTab(String text, Icon icon, JComponent contentComponent, JComponent titleComponent) {
		super(contentComponent);
		super.setOpaque(false);

		addFocusListener(new FocusListener() {
			public void focusGained(FocusEvent e) {
				repaint();
			}

			public void focusLost(FocusEvent e) {
				repaint();
			}
		});

		focusBorder = new FocusBorder(this);
		normalStatePanel = new StatePanel(focusBorder);
		highlightedStatePanel = new StatePanel(focusBorder);
		disabledStatePanel = new StatePanel(focusBorder);

		layout = new StackableLayout(this) {
			public void layoutContainer(Container parent) {
				super.layoutContainer(parent);
				StatePanel visibleStatePanel = (StatePanel) getVisibleComponent();
				visibleStatePanel.activateTitleComponent();
			}
		};

		setLayout(layout);

		add(normalStatePanel);
		add(highlightedStatePanel);
		add(disabledStatePanel);

		setText(text);
		setIcon(icon);
		setTitleComponent(titleComponent);

		eventPanel.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e) {
				updateFocus(TabSelectTrigger.MOUSE_PRESS);
			}

			public void mouseReleased(MouseEvent e) {
				updateFocus(TabSelectTrigger.MOUSE_RELEASE);
			}

			private void updateFocus(TabSelectTrigger trigger) {
				if (isEnabled() && properties.getFocusable() && getTabbedPanel() != null && getTabbedPanel().getProperties().getTabSelectTrigger() == trigger) {
					Component focusedComponent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();

					if (focusedComponent instanceof TitledTab && ((TitledTab) focusedComponent).getTabbedPanel() == getTabbedPanel())
						requestFocusInWindow();
					else if (isSelected() || TabbedUtils.getParentTabbedPanel(focusedComponent) != getTabbedPanel())
						requestFocusInWindow();
				}
			}
		});

		setEventComponent(eventPanel);

		MouseListener mouseListener = new MouseListener() {
			public void mouseClicked(MouseEvent e) {
				if (mouseListeners != null) {
					MouseEvent event = convertMouseEvent(e);
					Object[] l = mouseListeners.toArray();
					for (int i = 0; i < l.length; i++)
						((MouseListener) l[i]).mouseClicked(event);
				}
			}

			public void mousePressed(MouseEvent e) {
				if (mouseListeners != null) {
					MouseEvent event = convertMouseEvent(e);
					Object[] l = mouseListeners.toArray();
					for (int i = 0; i < l.length; i++)
						((MouseListener) l[i]).mousePressed(event);
				}
			}

			public void mouseReleased(MouseEvent e) {
				if (mouseListeners != null) {
					MouseEvent event = convertMouseEvent(e);
					Object[] l = mouseListeners.toArray();
					for (int i = 0; i < l.length; i++)
						((MouseListener) l[i]).mouseReleased(event);
				}
			}

			public void mouseEntered(MouseEvent e) {
				if (mouseListeners != null) {
					MouseEvent event = convertMouseEvent(e);
					Object[] l = mouseListeners.toArray();
					for (int i = 0; i < l.length; i++)
						((MouseListener) l[i]).mouseEntered(event);
				}
			}

			public void mouseExited(MouseEvent e) {
				if (mouseListeners != null) {
					MouseEvent event = convertMouseEvent(e);
					Object[] l = mouseListeners.toArray();
					for (int i = 0; i < l.length; i++)
						((MouseListener) l[i]).mouseExited(event);
				}
			}
		};

		MouseMotionListener mouseMotionListener = new MouseMotionListener() {
			public void mouseDragged(MouseEvent e) {
				if (mouseMotionListeners != null) {
					MouseEvent event = convertMouseEvent(e);
					Object[] l = mouseMotionListeners.toArray();
					for (int i = 0; i < l.length; i++)
						((MouseMotionListener) l[i]).mouseDragged(event);
				}
			}

			public void mouseMoved(MouseEvent e) {
				if (mouseMotionListeners != null) {
					MouseEvent event = convertMouseEvent(e);
					Object[] l = mouseMotionListeners.toArray();
					for (int i = 0; i < l.length; i++)
						((MouseMotionListener) l[i]).mouseMoved(event);
				}
			}
		};

		eventPanel.addMouseListener(mouseListener);
		eventPanel.addMouseMotionListener(mouseMotionListener);

		PropertyMapWeakListenerManager.addWeakTreeListener(properties.getMap(), propertiesListener);

		addTabListener(new TabAdapter() {
			public void tabAdded(TabEvent event) {
				PropertyMapWeakListenerManager.addWeakPropertyChangeListener(getTabbedPanel().getProperties().getMap(),
						TabbedPanelProperties.TAB_AREA_ORIENTATION, tabbedPanelPropertiesListener);
				updateTabAreaOrientation(getTabbedPanel().getProperties().getTabAreaOrientation());
			}

			public void tabRemoved(TabRemovedEvent event) {
				PropertyMapWeakListenerManager.removeWeakPropertyChangeListener(event.getTabbedPanel().getProperties().getMap(),
						TabbedPanelProperties.TAB_AREA_ORIENTATION, tabbedPanelPropertiesListener);
			}
		});

		doUpdateTab(null);
		updateCurrentStatePanel();
	}

	/**
	 * Gets the title component for the normal state
	 *
	 * @return title component or null if no title component
	 */
	public JComponent getNormalStateTitleComponent() {
		return normalStatePanel.getTitleComponent();
	}

	/**
	 * Gets the title component for the highlighted state
	 *
	 * @return title component or null if no title component
	 */
	public JComponent getHighlightedStateTitleComponent() {
		return highlightedStatePanel.getTitleComponent();
	}

	/**
	 * Gets the title component for the disabled state
	 *
	 * @return title component or null if no title component
	 */
	public JComponent getDisabledStateTitleComponent() {
		return disabledStatePanel.getTitleComponent();
	}

	/**
	 * <p>
	 * Sets the title component.
	 * </p>
	 *
	 * <p>
	 * This method is a convenience method for setting the same title component for all states.
	 * </p>
	 *
	 * @param titleComponent
	 *            the title component or null for no title component
	 */
	public void setTitleComponent(JComponent titleComponent) {
		normalStatePanel.setTitleComponent(titleComponent, properties.getNormalProperties());
		highlightedStatePanel.setTitleComponent(titleComponent, properties.getHighlightedProperties());
		disabledStatePanel.setTitleComponent(titleComponent, properties.getDisabledProperties());
	}

	/**
	 * Sets the normal state title component
	 *
	 * @param titleComponent
	 *            the title component or null for no title component
	 */
	public void setNormalStateTitleComponent(JComponent titleComponent) {
		normalStatePanel.setTitleComponent(titleComponent, properties.getNormalProperties());
	}

	/**
	 * Sets the highlighted state title component
	 *
	 * @param titleComponent
	 *            the title component or null for no title component
	 */
	public void setHighlightedStateTitleComponent(JComponent titleComponent) {
		highlightedStatePanel.setTitleComponent(titleComponent, properties.getHighlightedProperties());
	}

	/**
	 * Sets the disabled state title component
	 *
	 * @param titleComponent
	 *            the title component or null for no title component
	 */
	public void setDisabledStateTitleComponent(JComponent titleComponent) {
		disabledStatePanel.setTitleComponent(titleComponent, properties.getDisabledProperties());
	}

	/**
	 * <p>
	 * Sets if this TitledTab should be highlighted or not.
	 * </p>
	 *
	 * <p>
	 * <strong>Note:</strong> This will only have effect if this TitledTab is enabled and a member of a tabbed panel.
	 * </p>
	 *
	 * @param highlighted
	 *            true for highlight, otherwise false
	 */
	public void setHighlighted(boolean highlighted) {
		super.setHighlighted(highlighted);
		updateCurrentStatePanel();
	}

	/**
	 * <p>
	 * Sets if this TitledTab should be enabled or disabled
	 * </p>
	 * 
	 * <p>
	 * <strong>Note:</strong> since ITP 1.5.0 this method will change the enabled property in the {@link TitledTabProperties} for this tab. Enabled/disabled can
	 * be controlled by modifying the property or this method.
	 * </p>
	 *
	 * @param enabled
	 *            true for enabled, otherwise false
	 */
	public void setEnabled(boolean enabled) {
		super.setEnabled(enabled);
		updateCurrentStatePanel();
	}

	/**
	 * Gets the text for the normal state
	 *
	 * @return the text or null if no text
	 */
	public String getText() {
		return properties.getNormalProperties().getText();
	}

	/**
	 * Sets the text for the normal state
	 *
	 * @param text
	 *            the text or null for no text
	 */
	public void setText(String text) {
		properties.getNormalProperties().setText(text);
	}

	/**
	 * Gets the icon for the normal state
	 *
	 * @return the icon or null if none
	 */
	public Icon getIcon() {
		return properties.getNormalProperties().getIcon();
	}

	/**
	 * Sets the icon for the normal state
	 *
	 * @param icon
	 *            the icon or null for no icon
	 */
	public void setIcon(Icon icon) {
		properties.getNormalProperties().setIcon(icon);
	}

	/**
	 * Gets the TitledTabProperties
	 *
	 * @return the TitledTabProperties for this TitledTab
	 */
	public TitledTabProperties getProperties() {
		return properties;
	}

	/**
	 * Gets the text for the normal state.
	 *
	 * Same as getText().
	 *
	 * @return the text or null if no text
	 * @see #getText
	 * @since ITP 1.1.0
	 */
	public String toString() {
		return getText();
	}

	/**
	 * Adds a MouseListener to receive mouse events from this TitledTab.
	 *
	 * @param l
	 *            the MouseListener
	 */
	public synchronized void addMouseListener(MouseListener l) {
		if (mouseListeners == null)
			mouseListeners = new ArrayList(2);
		mouseListeners.add(l);
	}

	/**
	 * Removes a MouseListener
	 *
	 * @param l
	 *            the MouseListener to remove
	 */
	public synchronized void removeMouseListener(MouseListener l) {
		if (mouseListeners != null) {
			mouseListeners.remove(l);

			if (mouseListeners.size() == 0)
				mouseListeners = null;
		}
	}

	/**
	 * Gets the mouse listeners
	 *
	 * @return the mouse listeners
	 */
	public synchronized MouseListener[] getMouseListeners() {
		MouseListener[] listeners = new MouseListener[0];

		if (mouseListeners != null) {
			Object[] l = mouseListeners.toArray();
			listeners = new MouseListener[l.length];
			for (int i = 0; i < l.length; i++)
				listeners[i] = (MouseListener) l[i];
		}

		return listeners;
	}

	/**
	 * Adds a MouseMotionListener to receive mouse events from this TitledTab.
	 *
	 * @param l
	 *            the MouseMotionListener
	 */
	public synchronized void addMouseMotionListener(MouseMotionListener l) {
		if (mouseMotionListeners == null)
			mouseMotionListeners = new ArrayList(2);

		mouseMotionListeners.add(l);
	}

	/**
	 * Removes a MouseMotionListener
	 *
	 * @param l
	 *            the MouseMotionListener to remove
	 */
	public synchronized void removeMouseMotionListener(MouseMotionListener l) {
		if (mouseMotionListeners != null) {
			mouseMotionListeners.remove(l);

			if (mouseMotionListeners.size() == 0)
				mouseMotionListeners = null;
		}
	}

	/**
	 * Gets the mouse motion listeners
	 *
	 * @return the mouse motion listeners
	 */
	public synchronized MouseMotionListener[] getMouseMotionListeners() {
		MouseMotionListener[] listeners = new MouseMotionListener[0];

		if (mouseMotionListeners != null) {
			Object[] l = mouseMotionListeners.toArray();
			listeners = new MouseMotionListener[l.length];
			for (int i = 0; i < l.length; i++)
				listeners[i] = (MouseMotionListener) l[i];
		}

		return listeners;
	}

	/**
	 * Gets the Shape for the current active rendering state.
	 *
	 * @return the Shape for the active rendering state, null if no special shape
	 * @since ITP 1.2.0
	 */
	public Shape getShape() {
		Shape shape = currentStatePanel.getShape();

		if (shape == null)
			return null;

		Point p = SwingUtilities.convertPoint(currentStatePanel, 0, 0, this);
		return new TranslatingShape(shape, p.x, p.y);
	}

	protected void setTabbedPanel(TabbedPanel tabbedPanel) {
		if (tabbedPanel == null)
			HoverManager.getInstance().removeHoverable(eventPanel);

		super.setTabbedPanel(tabbedPanel);

		if (tabbedPanel != null)
			HoverManager.getInstance().addHoverable(eventPanel);
	}

	private Insets getBorderInsets(Border border) {
		return border == null ? InsetsUtil.EMPTY_INSETS : border.getBorderInsets(this);
	}

	private void updateBorders() {
		Direction tabAreaOrientation = getTabAreaOrientation();
		int raised = properties.getHighlightedRaised();
		Insets notRaised = InsetsUtil.setInset(InsetsUtil.EMPTY_INSETS, tabAreaOrientation, raised);
		Border normalBorder = new EmptyBorder(notRaised);

		Insets maxInsets = properties.getBorderSizePolicy() == TitledTabBorderSizePolicy.INDIVIDUAL_SIZE ? null : InsetsUtil.max(
				getBorderInsets(properties.getNormalProperties().getComponentProperties().getBorder()),
				InsetsUtil.max(getBorderInsets(properties.getHighlightedProperties().getComponentProperties().getBorder()), getBorderInsets(properties
						.getDisabledProperties().getComponentProperties().getBorder())));

		Insets normalInsets = InsetsUtil.rotate(properties.getNormalProperties().getDirection(), properties.getNormalProperties().getComponentProperties()
				.getInsets());

		Insets disabledInsets = InsetsUtil.rotate(properties.getDisabledProperties().getDirection(), properties.getDisabledProperties()
				.getComponentProperties().getInsets());

		int edgeInset = Math.min(InsetsUtil.getInset(normalInsets, tabAreaOrientation.getOpposite()),
				InsetsUtil.getInset(disabledInsets, tabAreaOrientation.getOpposite()));

		int normalLowered = Math.min(edgeInset, raised);

		Border innerNormalBorder = getInnerBorder(properties.getNormalProperties(), tabAreaOrientation, -normalLowered, maxInsets);
		Border innerHighlightBorder = getInnerBorder(properties.getHighlightedProperties(), tabAreaOrientation, raised - normalLowered, maxInsets);
		Border innerDisabledBorder = getInnerBorder(properties.getDisabledProperties(), tabAreaOrientation, -normalLowered, maxInsets);

		normalStatePanel.setBorders(normalBorder, innerNormalBorder);
		highlightedStatePanel.setBorders(null, innerHighlightBorder);
		disabledStatePanel.setBorders(normalBorder, innerDisabledBorder);
	}

	private void doUpdateTab(Map changes) {
		boolean updateBorders = false;

		if (changes == null) {
			// Init all
			updateBorders = true;

			setFocusableComponent(properties.getFocusable() ? this : null);
			focusBorder.setEnabled(properties.getFocusMarkerEnabled());

			updateHoverListener(properties.getHoverListener());
			layout.setUseSelectedComponentSize(properties.getSizePolicy() == TitledTabSizePolicy.INDIVIDUAL_SIZE);
		} else {
			Map m = (Map) changes.get(properties.getMap());
			if (m != null) {
				Set keySet = m.keySet();

				if (keySet.contains(TitledTabProperties.FOCUSABLE)) {
					setFocusableComponent(properties.getFocusable() ? this : null);
				}

				if (keySet.contains(TitledTabProperties.FOCUS_MARKER_ENABLED)) {
					focusBorder.setEnabled(properties.getFocusMarkerEnabled());
					currentStatePanel.getLabel().repaint();
				}

				if (keySet.contains(TitledTabProperties.HOVER_LISTENER)) {
					updateHoverListener((HoverListener) ((ValueChange) m.get(TitledTabProperties.HOVER_LISTENER)).getNewValue());
				}

				if (keySet.contains(TitledTabProperties.SIZE_POLICY)) {
					layout.setUseSelectedComponentSize(((TitledTabSizePolicy) ((ValueChange) m.get(TitledTabProperties.SIZE_POLICY)).getNewValue()) == TitledTabSizePolicy.INDIVIDUAL_SIZE);
				}

				if (keySet.contains(TitledTabProperties.HIGHLIGHTED_RAISED_AMOUNT) || keySet.contains(TitledTabProperties.BORDER_SIZE_POLICY)) {
					updateBorders = true;
				}

				if (keySet.contains(TitledTabProperties.ENABLED)) {
					doSetEnabled(properties.getEnabled());
				}
			}
		}

		updateBorders = normalStatePanel.updateState(changes, properties.getNormalProperties()) || updateBorders;
		updateBorders = highlightedStatePanel.updateState(changes, properties.getHighlightedProperties()) || updateBorders;
		updateBorders = disabledStatePanel.updateState(changes, properties.getDisabledProperties()) || updateBorders;

		if (updateBorders)
			updateBorders();
	}

	private void updateHoverListener(HoverListener newHoverListener) {
		HoverListener oldHoverListener = hoverListener;
		hoverListener = newHoverListener;
		if (HoverManager.getInstance().isHovered(eventPanel)) {
			if (oldHoverListener != null)
				oldHoverListener.mouseExited(new HoverEvent(TitledTab.this));
			if (hoverListener != null)
				hoverListener.mouseEntered(new HoverEvent(TitledTab.this));
		}
	}

	private Border getInnerBorder(TitledTabStateProperties properties, Direction tabOrientation, int raised, Insets maxInsets) {
		Direction tabDir = properties.getDirection();
		Insets insets = InsetsUtil.rotate(tabDir, properties.getComponentProperties().getInsets());

		if (maxInsets != null)
			insets = InsetsUtil.add(insets, InsetsUtil.sub(maxInsets, getBorderInsets(properties.getComponentProperties().getBorder())));

		Border border = properties.getComponentProperties().getBorder();
		Border innerBorder = new EmptyBorder(InsetsUtil.add(insets, InsetsUtil.setInset(InsetsUtil.EMPTY_INSETS, tabOrientation.getOpposite(), raised)));
		return border == null ? innerBorder : new CompoundBorder(border, innerBorder);
	}

	private Direction getTabAreaOrientation() {
		return getTabbedPanel() == null ? lastTabAreaOrientation : getTabbedPanel().getProperties().getTabAreaOrientation();
	}

	private void updateTabAreaOrientation(Direction newDirection) {
		if (lastTabAreaOrientation != newDirection) {
			lastTabAreaOrientation = newDirection;
			updateBorders();

			normalStatePanel.updateShapedPanel(properties.getNormalProperties());
			highlightedStatePanel.updateShapedPanel(properties.getHighlightedProperties());
			disabledStatePanel.updateShapedPanel(properties.getDisabledProperties());
		}
	}

	private void updateCurrentStatePanel() {
		StatePanel newStatePanel = normalStatePanel;
		if (!isEnabled())
			newStatePanel = disabledStatePanel;
		else if (isHighlighted())
			newStatePanel = highlightedStatePanel;

		eventPanel.setToolTipText(newStatePanel.getToolTipText());

		if (currentStatePanel != newStatePanel) {
			if (currentStatePanel != null)
				currentStatePanel.deactivate();
			currentStatePanel = newStatePanel;
			currentStatePanel.activate();
		}
		layout.showComponent(currentStatePanel);
	}

	private MouseEvent convertMouseEvent(MouseEvent e) {
		Point p = SwingUtilities.convertPoint((JComponent) e.getSource(), e.getPoint(), TitledTab.this);
		return new MouseEvent(TitledTab.this, e.getID(), e.getWhen(), e.getModifiers(), (int) p.getX(), (int) p.getY(), e.getClickCount(), !e.isConsumed()
				&& e.isPopupTrigger(), e.getButton());
	}

	private void doSetEnabled(boolean enabled) {
		super.setEnabled(enabled);
		updateCurrentStatePanel();
	}

	public void setUI(PanelUI ui) {
		if (getUI() != UI)
			super.setUI(UI);
	}

	public void updateUI() {
		setUI(UI);
	}

	public void setOpaque(boolean opaque) {
		// Ignore
	}

}